import lvgl as lv
import network
import socket
import ure
import time
from imgUtil import showImg

ap_ssid = "智能摆台"
ap_password = ""
ap_authmode = network.AUTH_OPEN  # WPA2

NETWORK_PROFILES = 'wifi.dat'

wlan_ap = network.WLAN(network.AP_IF)
wlan_sta = network.WLAN(network.STA_IF)

server_socket = None



def unquote(string):
    """unquote('abc%20def') -> b'abc def'.

    Note: if the input is a str instance it is encoded as UTF-8.
    This is only an issue if it contains unescaped non-ASCII characters,
    which URIs should not.
    """
    if not string:
        return b''

    if isinstance(string, str):
        string = string.encode('utf-8')

    bits = string.split(b'%')
    if len(bits) == 1:
        return string

    res = bytearray(bits[0])
    append = res.append
    extend = res.extend

    for item in bits[1:]:
        try:
            append(int(item[:2], 16))
            extend(item[2:])
        except KeyError:
            append(b'%')
            extend(item)

    return bytes(res)

def get_connection():
    """return a working WLAN(STA_IF) instance or None"""

    # First check if there already is any connection:
    if wlan_sta.isconnected():
        return wlan_sta

    connected = False
    try:
        # ESP connecting to WiFi takes time, wait a bit and try again:
        time.sleep(3)
        if wlan_sta.isconnected():
            return wlan_sta

        # Read known network profiles from file
        profiles = read_profiles()
        
        # Search WiFis in range
        wlan_sta.active(True)
        networks = wlan_sta.scan()
        AUTHMODE = {0: "open", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"}
        for ssid, bssid, channel, rssi, authmode, hidden in sorted(networks, key=lambda x: x[3], reverse=True):
            ssid = ssid.decode('utf-8')
            encrypted = authmode > 0
            print("ssid: %s chan: %d rssi: %d authmode: %s" % (ssid, channel, rssi, AUTHMODE.get(authmode, '?')))
            if encrypted:
                if ssid in profiles:
                    password = profiles[ssid]
                    connected = do_connect(ssid, password)
                else:
                    print("skipping unknown encrypted network")
            else:  # open
                connected = do_connect(ssid, None)
            if connected:
                break

    except OSError as e:
        print("exception", str(e))

    # start web server for connection manager:
    if not connected:
        connected = start()

    return wlan_sta if connected else None


def read_profiles():
    with open(NETWORK_PROFILES) as f:
        lines = f.readlines()
    profiles = {}
    for line in lines:
        ssid, password = line.strip("\n").split(";")
        profiles[ssid] = password
    return profiles


def write_profiles(profiles):
    lines = []
    for ssid, password in profiles.items():
        lines.append("%s;%s\n" % (ssid, password))
    with open(NETWORK_PROFILES, "w") as f:
        f.write(''.join(lines))


def do_connect(ssid, password):
    wlan_sta.active(True)
    wlan_sta.config(reconnects=0)
    if wlan_sta.isconnected():
        return None
    print('Trying to connect to %s...' % ssid)
    wlan_sta.connect(ssid, password)
    for retry in range(100):
        connected = wlan_sta.isconnected()
        if connected:
            break
        time.sleep(0.1)
        print('.', end='')
    if connected:
        print('\nConnected. Network config: ', wlan_sta.ifconfig())
    else:
        print('\nFailed. Not Connected to: ' + ssid)
    return connected


def send_header(client, status_code=200, content_length=None ):
    client.sendall("HTTP/1.0 {} OK\r\n".format(status_code))
    client.sendall("Content-Type: text/html\r\n")
    if content_length is not None:
      client.sendall("Content-Length: {}\r\n".format(content_length))
    client.sendall("\r\n")


def send_response(client, payload, status_code=200):
    content_length = len(payload)
    send_header(client, status_code, content_length)
    if content_length > 0:
        client.sendall(payload)
    client.close()


def handle_root(client):
    wlan_sta.active(True)
    print(wlan_sta.scan())
    ssids = sorted(ssid.decode('utf-8') for ssid, *_ in wlan_sta.scan())
    send_header(client)
    client.sendall("""\
            <!doctype html>
            <html lang="zh">
            <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
            <title>智能摆台</title>

            <style>
            *{padding:0;margin:0;}
            .content {
                margin:50px auto 0;
                width:300px;
                min-height: 500px;
            }

            .form-group {
                width:100%;
                float:left;
                margin:5px 0;
            }

            label{
                margin-bottom:10px;
                float:left;			
            }

            .field-input, select{
                width:calc(100% - 20px);
                float:left;
                padding:10px;
                font-family:inherit;
            }
            body{
                background: #494A5F;
                color: #D5D6E2;
                font-weight: 500;
                font-size: 1.05em;
            }
            a{color: #2fa0ec;text-decoration: none;outline: none;}
            a:hover,a:focus{color:#74777b;}

            .container{
                margin: 0 auto;
                overflow: hidden;
            }.pk-input {
                width:calc(100% - 16px);
                float:left;
                cursor:pointer;			
                padding:8px;
            }

            .pk-input.-clean{
                background:#fff;
                border:1px solid #eee;
                font:inherit;
            }

            .pk-input.-dark{
                background:#666;
                color: #eee;
                border:1px solid #999;
                font:inherit;
            }

            .pk-arrow{
                position:absolute;
                cursor:pointer;			
                right:-2px;
                bottom:0;
                text-align:center;	
                width:30px;	
                padding-bottom:2px;		
            }

            .pk-arrow.-clean{
                background:#eee;
                color:#aaa;			
            }

            .pk-arrow.-dark{
                background:#666;
                color: #eee;
            }

            .pk-input.-clean:hover + .pk-arrow, .pk-arrow.-clean:hover{
                background:#dfdfdf;
                color:#aaa;			
            }

            .pk-input.-dark:hover + .pk-arrow, .pk-arrow.-dark:hover{
                background:#000;
                color:#ccc;			
            }

            .pk-arrow:before{
                transform: rotate(90deg);	
                font-size:25px;	
                position:relative;
                content:'';
                margin:5px 0 0 13px;
                float:left;
            }

            .pk-overlay {
                width:100%;
                height:100%;
                position:fixed;
                background:#666;
                background:rgba(0,0,0,0.4);
                top:0;
                left:0;
                opacity:0;
                visibility:hidden;
                z-index:1110;
                transition:opacity .3s;
            }

            .pk-overlay.-show {
                opacity:1;
                visibility:visible;
            }

            .pk-modal {
                transform: translateX(-50%) scale(0.8,0.8);
                left:50%;
                top:30px;
                border-radius:3px;
                position:absolute;
                width:280px;
                max-height:90%;
                overflow-y:auto;
                z-index:1111;			
                transition: all .3s;
                visibility:hidden;		
                background:#f2f2f2;
                opacity:0;
            }

            .pk-modal > .head{
                width:calc(100% - 20px);
                font-size:18px;
                padding:20px 10px;
            }

            .pk-modal.-clean{
                background:#fff;
                border-bottom:10px solid #efefef;
            }

            .pk-modal.-clean > .head{
                color:#777;
                background:#efefef;
            }

            .pk-modal.-dark{
                color:#f2f2f2;
                background:#444;
                border-bottom:10px solid #333;	
            }

            .pk-modal.-dark > .head{
                color:#f2f2f2;
                background:#333;
            }

            .pk-modal.-show {
                transform: translateX(-50%) scale(1,1);
                visibility:visible;		
                opacity:1;
                box-shadow: 1px 1px 20px 2px #888;
            }

            .pk-search{
                display:none;
            }

            .pk-search.-show{
                display:block;
            }

            .pk-search > input{
                width:calc(100% - 22px);
                padding:8px 4px;
                margin:6px;
                border:1px solid #ddd;
            }

            .pk-no_result_search{
                padding:10px;
                font-size:14px;
            }

            .pk-modal > .close{
                position:absolute;
                top:18px;
                right:15px;
                color:#ccc;	
                font-size:22px;	
                cursor:pointer;
            }

            .pk-option{
                cursor:pointer;
                padding:14px 10px;
                width:calc(100% - 20px);	
                border-bottom:1px solid #efefef;
            }

            .pk-option.-clean{	
                border-bottom:1px solid #efefef;
            }

            .pk-option.-dark{	
                border-bottom:1px solid #333;
                color:#f2f2f2;
            }

            .pk-option.-clean:hover, .pk-option.-clean.-selected{
                background:#f2f2f2;			
            }

            .pk-option.-dark:hover, .pk-option.-dark.-selected{
                background:#333;			
            }

            .pk-option:last-child{
                border:none;
            }

            .pk-option > .icon:not(:empty){
                width:16px;
                margin-right:10px;
                color:#ccc;
                font-size:20px;
                float:left;
                margin-top:-3px;
                text-align:center;
            }

            .pk-option > .txt{
                color:inherit;
            }

            .pk-modal > .main{
                list-style:none;
            }
            </style>

            </head>
            <body>
            <div class="container">
                <div class="content">		
                    <form action="/configure" method="post">
                        <div class="form-group">
                            <label for="city">WIFI:</label>
                            <select name="ssid" id="city" class="city pickout" placeholder="选择一个WIFI">
    """)
    while len(ssids):
        ssid = ssids.pop(0)
        if len(ssid)==0:
            continue 
        
        client.sendall("""\
                        	<option data-icon="&#9786;" value="{0}">{0}</option>
        """.format(ssid))
    client.sendall("""\
                        </select>			
                        </div>
                        <div class="form-group">
                            <label for="name2">密码:</label>
                            <input type="text" name="password" id="password" class="field-input">			
                        </div>
                        <div class="form-group" >
                            <input type="submit" style="width: 102%;" value="提交" class="field-input">			
                        </div>
                    </form>
                </div>
            </div>

            <script >
            /*
            *	Pickout - Cool effect for field select on form
            *	Copyright (c) 2016 Ktquez
            *	Project repository: https://github.com/ktquez/pickout
            * 	MIT license.
            */

            var pickout = (function(){

                "use strict";

                // Own configuration of each field select
                var ownConfig = {};

                // Default values
                var defaults = {
                    theme : 'clean',
                    search : false
                };

                /**
                 * Starts the module preparing the elements
                 * @param config = String or object to setting
                 */
                function init(config){
                    setElement(config);
                    prepareElement();
                }

                /**
                 * Defines the own configuration and assigns the select
                 * @param {[type]} config = String or object to setting
                 */
                function setElement(config){

                    var objConfig = typeof config === 'object' ? config : {};

                    if (typeof config === 'string') {
                        objConfig.el = config;
                    }

                    // Retrieve the DOM to be manipulated
                    objConfig.DOM = [].slice.call(document.querySelectorAll(objConfig.el));

                    mergeToDefaults(objConfig);		
                }


                /**
                 * Prepare the elements that will be handled by the module
                 */
                function prepareElement(){

                    ownConfig.DOM.map(function(select, index){
                        createElements(select, index);
                    });

                    prepareModal();
                }

                function createElements(select, index){

                    // Cache self config 
                    var config = ownConfig;
                    
                    select.style.display = 'none';

                    var parent = select.parentElement;
                    parent.setAttribute('style', 'position:relative;float:left;');
                    var placeholder = select.getAttribute('placeholder');

                    // input
                    var input = document.createElement('input');
                    input.setAttribute('readonly', 'readonly');
                    input.setAttribute('class', 'pk-input -'+ config.theme);
                    if(!!placeholder) input.setAttribute('placeholder', placeholder);

                    if(parent.hasAttribute('for')) input.setAttribute('id', parent.getAttribute('for'));
                    
                    // Arrow
                    var arrow = document.createElement('span');
                    arrow.setAttribute('class', 'pk-arrow -'+ config.theme);

                    parent.appendChild(input);
                    parent.appendChild(arrow);

                    // Event listener
                    parent.addEventListener('click', function(e){
                        e.preventDefault();
                        e.stopPropagation();

                        config.currentIndex = index;
                        fireModal(config);
                    }, false);


                }

                /**
                 * Create and manage options in modal
                 * @param  {Object} config = ownConfig
                 */
                function fireModal(config){

                    var modal = document.querySelector('.pk-modal'),
                        select = config.DOM[config.currentIndex],
                        data;

                    // modal theme
                    modal.setAttribute('class', modal.getAttribute('class')+' -'+config.theme);

                    // Avoid charging again when changing tab and the field gives focus again
                    var main = modal.querySelector('.main');
                    if (!!main.children.length) {
                        return;
                    }

                    var overlay = document.querySelector('.pk-overlay');
                    var options = [].slice.call(select);
                
                    var optionsModal = options.map(function(option, key){
                        data = {index: key, item: option};
                        return createOption(data, modal, config);
                    });

                    // Displaying overlay and modal
                    modal.setAttribute('class', modal.getAttribute('class') + ' -show');
                    overlay.setAttribute('class', overlay.getAttribute('class') + ' -show');

                    var title = select.hasAttribute('placeholder') ? select.getAttribute('placeholder') : 'Select to option';
                    modal.querySelector('.head').innerHTML = title;

                    // If search
                    if(config.search) {
                        var search = modal.querySelector('.pk-search');
                        var inputSearch = search.querySelector('input');
                        inputSearch.value = '';

                        // Focus no field search
                        setTimeout(function(){
                            inputSearch.focus();
                        }, 300);

                        search.setAttribute('class', search.getAttribute('class') + ' -show');

                        // Listener
                        inputSearch.addEventListener('keyup', function(e) {
                            e.preventDefault();
                            e.stopPropagation();

                            var optionsDefault = optionsModal,
                                main = modal.querySelector('.main');

                            // Specific for IE
                            if (!!document.documentMode) {
                                optionsModal.map(function(option){
                                    option.style.display = 'none';
                                });

                            }else {
                                // Deletes the options
                                main.innerHTML = '';
                            }


                            // If the search field is empty
                            if(!e.target.value) {
                                optionsDefault.map(function(option){

                                    // Specific for IE
                                    if (!!document.documentMode) {
                                        option.style.display = 'block';
                                        return;
                                    }

                                    main.appendChild(option);
                                });
                                return;
                            }	 

                            // If any character typed
                            optionsModal.map(function(option){
                                // Recover text element
                                var txt = option.children[1] || option.children[0];
                                // Compares the search with the text option
                                if(txt.innerHTML.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1) {

                                    // Specific for IE
                                    if (!!document.documentMode) {
                                        option.style.display = 'block';
                                        return;
                                    }

                                    main.appendChild(option);						
                                }
                            });

                            // No results
                            if (!main.children.length) {
                                var noResults = document.createElement('li');
                                noResults.setAttribute('class', 'pk-no_result_search');
                                noResults.innerHTML = 'No Results';

                                main.appendChild(noResults);
                                return;
                            }
                            
                        }, false);
                    }
                    
                }

                /**
                 * Creates options for the chosen select
                 * @param  {Object} data = {index, option}
                 * @param  {object DOM} modal  = Modal that will receive the list
                 * @param  {object} config 
                 */
                function createOption(data, modal, config){

                    var select = config.DOM[config.currentIndex];		
                    var main = modal.querySelector('.main');

                    var item = document.createElement('li');
                    var selected = data.item.hasAttribute('selected') ? '-selected' : '';
                    item.setAttribute('class', 'pk-option '+ selected +' -'+config.theme);

                    var icon = document.createElement('span');
                    icon.setAttribute('class', 'icon');
                    icon.innerHTML = data.item.getAttribute('data-icon') || '';

                    var txt = document.createElement('span');
                    txt.setAttribute('class', 'txt');
                    txt.innerHTML = data.item.innerHTML;

                    main.appendChild(item);
                    item.appendChild(icon);
                    item.appendChild(txt);

                    // Event listener
                    item.addEventListener('click', function(e){
                        e.preventDefault();
                        e.stopPropagation();

                        // Converting to array, because it is a (object) HTMLCollection 
                        [].slice.call(select.children).map(function(item, index){
                            if (index === data.index) {
                                item.setAttribute('selected', 'selected');
                                return;
                            }

                            item.removeAttribute('selected');
                        });
                        
                        feedInput(select, txt.innerHTML);		
                        closeModal();
                    }, false);

                    return item;
                }

                function feedInput(select, value){
                    select.parentElement.querySelector('.pk-input').value = value;
                }

                /**
                 * Sets a value (option) default for field select
                 */
                function setInitialValue(config){
                    setElement(config);	

                    ownConfig.DOM.map(function(select){
                        feedInput(select, select[select.selectedIndex].innerHTML);
                    });
                }

                /**
                 * Prepare the divs that will be used for modal with options
                 */
                function prepareModal(){

                    // Checks has been created
                    if (document.querySelector('.pk-overlay')) {
                        return;
                    }	

                    var overlay = document.createElement('div');
                    overlay.setAttribute('class', 'pk-overlay');

                    var modal = document.createElement('div');
                    modal.setAttribute('class', 'pk-modal');		

                    var mainModal = document.createElement('ul');
                    mainModal.setAttribute('class', 'main');

                    var head = document.createElement('div');
                    head.setAttribute('class', 'head');

                    var search = document.createElement('div');
                    search.setAttribute('class', 'pk-search');	
                    var inputSearch = document.createElement('input');
                    inputSearch.setAttribute('type', 'text');

                    var close = document.createElement('span');
                    close.setAttribute('class', 'close');
                    close.innerHTML = '&times;';

                    document.body.appendChild(overlay);
                    document.body.appendChild(modal);
                    modal.appendChild(head);
                    modal.appendChild(search);
                    search.appendChild(inputSearch);
                    modal.appendChild(close);
                    modal.appendChild(mainModal);

                    // Event listener
                    overlay.addEventListener('click', function(e){
                        e.preventDefault();
                        e.stopPropagation();

                        closeModal();
                    }, false);

                    close.addEventListener('click', function(e){
                        e.preventDefault();
                        e.stopPropagation();

                        closeModal();
                    }, false);

                }	

                /**
                 * Resume normal classes and removes the content from within the modal
                 * @param  {object DOM} overlay
                 * @param  {object DOM} modal
                 */
                function closeModal(){
                    var overlay = document.querySelector('.pk-overlay');
                    var modal = document.querySelector('.pk-modal');
                    var search = modal.querySelector('.pk-search');

                    overlay.setAttribute('class', 'pk-overlay');
                    modal.setAttribute('class', 'pk-modal');
                    search.setAttribute('class', 'pk-search');
                    setTimeout(function(){
                        modal.querySelector('.main').innerHTML = '';
                    }, 500);
                }

                /**
                 * Merges the settings passed by the user with the default settings of the package, adding their own configurations
                 */
                function mergeToDefaults(config){
                    
                    ownConfig = JSON.parse(JSON.stringify(defaults));

                    for (var item in config) {
                        if (config.hasOwnProperty(item)) {
                            ownConfig[item] = config[item];
                        }
                    }

                }

                // Revealing the methods that shall be public
                return {
                    to : init,
                    updated : setInitialValue
                };

            })();

            if (typeof module !== 'undefined' && module.exports) {
                module.exports = pickout;
            }

            </script>
            <script>

                // Preparar o select
                //pickout.to('.pickout');
                pickout.to({
                    el:'.city',
                    theme: 'dark', 
                    search: true
                });

                pickout.to({
                    el:'.state',
                    theme: 'clean',
                });

                // Caso o valor já venha do servidor, já atribui a seleção automaticamente
                pickout.updated('.city');
                
            </script>

            </body>
            </html>
    """)
    client.close()


def handle_configure(client, request):
    match = ure.search("ssid=([^&]*)&password=(.*)", request)
    if match is None:
        send_response(client, "Parameters not found", status_code=400)
        return False
    # version 1.9 compatibility
    try:
        ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!")
        ssid = bytes.decode(unquote(ssid))
        password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!")
    except Exception:
        ssid = match.group(1).replace("%3F", "?").replace("%21", "!")
        password = match.group(2).replace("%3F", "?").replace("%21", "!")

    if len(ssid) == 0:
        send_response(client, "SSID must be provided", status_code=400)
        return False

    if do_connect(ssid, password):
        response = """\
            <html>
                <meta charset="UTF-8">
                <meta http-equiv="X-UA-Compatible" content="IE=edge">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <center>
                    <br><br>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            连接成功 %(ssid)s. <br> 现在可以退出浏览器了
                        </span>
                    </h1>
                    <br><br>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        try:
            profiles = read_profiles()
        except OSError:
            profiles = {}
        profiles[ssid] = password
        write_profiles(profiles)

        time.sleep(5)

        return True
    else:
        response = """\
            <html>
                  <meta charset="UTF-8">
                <meta http-equiv="X-UA-Compatible" content="IE=edge">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <center>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            连接这个WIFI失败 %(ssid)s.
                        </span>
                    </h1>
                    <br><br>
                        <a href="/">返回</a>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        return False


def handle_not_found(client, url):
    send_response(client, "Path not found: {}".format(url), status_code=404)


def stop():
    global server_socket

    if server_socket:
        server_socket.close()
        server_socket = None


def start(port=80):
    global server_socket
    addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]
    stop()
    wlan_sta.active(True)
    wlan_ap.active(True)
    print(ap_ssid,ap_password,ap_authmode)
    wlan_ap.config(essid=ap_ssid, password=ap_password, authmode=ap_authmode)
    server_socket = socket.socket()
    server_socket.bind(addr)
    server_socket.listen(1)
    
    showImg("./img/tips.png");
    
    print('Connect to WiFi ssid ' + ap_ssid + ', default password: ' + ap_password)
    print('and access the ESP via your favorite web browser at 192.168.4.1.')
    print('Listening on:', addr)

    while True:
        if wlan_sta.isconnected():
            return True

        client, addr = server_socket.accept()
        print('client connected from', addr)
        try:
            client.settimeout(5.0)

            request = b""
            try:
                while "\r\n\r\n" not in request:
                    request += client.recv(512)
            except OSError:
                pass

            print("Request is: {}".format(request))
            if "HTTP" not in request:  # skip invalid requests
                continue

            # version 1.9 compatibility
            try:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/")
            except Exception:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/")
            print("URL is {}".format(url))

            if url == "":
                handle_root(client)
            elif url == "configure":
                handle_configure(client, request)
            else:
                handle_not_found(client, url)

        finally:
            client.close()